/*
 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
 *
 * This software is open source.
 * See the bottom of this file for the licence.
 */

package com.googlecode.bluetools.dom4j;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.List;
import java.util.Map;

import org.jaxen.VariableContext;

import com.googlecode.bluetools.dom4j.rule.Pattern;
import com.googlecode.bluetools.dom4j.tree.AbstractDocument;
import com.googlecode.bluetools.dom4j.tree.DefaultAttribute;
import com.googlecode.bluetools.dom4j.tree.DefaultCDATA;
import com.googlecode.bluetools.dom4j.tree.DefaultComment;
import com.googlecode.bluetools.dom4j.tree.DefaultDocument;
import com.googlecode.bluetools.dom4j.tree.DefaultDocumentType;
import com.googlecode.bluetools.dom4j.tree.DefaultElement;
import com.googlecode.bluetools.dom4j.tree.DefaultEntity;
import com.googlecode.bluetools.dom4j.tree.DefaultProcessingInstruction;
import com.googlecode.bluetools.dom4j.tree.DefaultText;
import com.googlecode.bluetools.dom4j.tree.QNameCache;
import com.googlecode.bluetools.dom4j.util.SimpleSingleton;
import com.googlecode.bluetools.dom4j.util.SingletonStrategy;
import com.googlecode.bluetools.dom4j.xpath.DefaultXPath;
import com.googlecode.bluetools.dom4j.xpath.XPathPattern;

/**
 * <p>
 * <code>DocumentFactory</code> is a collection of factory methods to allow easy custom building of DOM4J trees. The
 * default tree that is built uses a doubly linked tree.
 * </p>
 * 
 * <p>
 * The tree built allows full XPath expressions from anywhere on the tree.
 * </p>
 * 
 * @author <a href="mailto:jstrachan@apache.org">James Strachan </a>
 */
public class DocumentFactory implements Serializable {
	private static SingletonStrategy singleton = null;

	protected transient QNameCache cache;

	/** Default namespace prefix -> URI mappings for XPath expressions to use */
	private Map xpathNamespaceURIs;

	private static SingletonStrategy createSingleton() {
		SingletonStrategy result = null;

		String documentFactoryClassName;
		try {
			documentFactoryClassName = System.getProperty("com.bluebamboo.dom4j.factory",
					"com.bluebamboo.dom4j.DocumentFactory");
		}
		catch (Exception e) {
			documentFactoryClassName = "com.bluebamboo.dom4j.DocumentFactory";
		}

		try {
			String singletonClass = System.getProperty("com.bluebamboo.dom4j.DocumentFactory.singleton.strategy",
					"com.bluebamboo.dom4j.util.SimpleSingleton");
			Class clazz = Class.forName(singletonClass);
			result = (SingletonStrategy) clazz.newInstance();
		}
		catch (Exception e) {
			result = new SimpleSingleton();
		}

		result.setSingletonClassName(documentFactoryClassName);

		return result;
	}

	public DocumentFactory() {
		init();
	}

	/**
	 * <p>
	 * Access to singleton implementation of DocumentFactory which is used if no DocumentFactory is specified when
	 * building using the standard builders.
	 * </p>
	 * 
	 * @return the default singleon instance
	 */
	public static synchronized DocumentFactory getInstance() {
		if (singleton == null) {
			singleton = createSingleton();
		}
		return (DocumentFactory) singleton.instance();
	}

	// Factory methods
	public Document createDocument() {
		DefaultDocument answer = new DefaultDocument();
		answer.setDocumentFactory(this);

		return answer;
	}

	/**
	 * DOCUMENT ME!
	 * 
	 * @param encoding DOCUMENT ME!
	 * 
	 * @return DOCUMENT ME!
	 * 
	 * @since 1.5
	 */
	public Document createDocument(String encoding) {
		// to keep the DocumentFactory backwards compatible, we have to do this
		// in this not so nice way, since subclasses only need to extend the
		// createDocument() method.
		Document answer = createDocument();

		if (answer instanceof AbstractDocument) {
			((AbstractDocument) answer).setXMLEncoding(encoding);
		}

		return answer;
	}

	public Document createDocument(Element rootElement) {
		Document answer = createDocument();
		answer.setRootElement(rootElement);

		return answer;
	}

	public DocumentType createDocType(String name, String publicId, String systemId) {
		return new DefaultDocumentType(name, publicId, systemId);
	}

	public Element createElement(QName qname) {
		return new DefaultElement(qname);
	}

	public Element createElement(String name) {
		return createElement(createQName(name));
	}

	public Element createElement(String qualifiedName, String namespaceURI) {
		return createElement(createQName(qualifiedName, namespaceURI));
	}

	public Attribute createAttribute(Element owner, QName qname, String value) {
		return new DefaultAttribute(qname, value);
	}

	public Attribute createAttribute(Element owner, String name, String value) {
		return createAttribute(owner, createQName(name), value);
	}

	public CDATA createCDATA(String text) {
		return new DefaultCDATA(text);
	}

	public Comment createComment(String text) {
		return new DefaultComment(text);
	}

	public Text createText(String text) {
		if (text == null) {
			String msg = "Adding text to an XML document must not be null";
			throw new IllegalArgumentException(msg);
		}

		return new DefaultText(text);
	}

	public Entity createEntity(String name, String text) {
		return new DefaultEntity(name, text);
	}

	public Namespace createNamespace(String prefix, String uri) {
		return Namespace.get(prefix, uri);
	}

	public ProcessingInstruction createProcessingInstruction(String target, String data) {
		return new DefaultProcessingInstruction(target, data);
	}

	public ProcessingInstruction createProcessingInstruction(String target, Map data) {
		return new DefaultProcessingInstruction(target, data);
	}

	public QName createQName(String localName, Namespace namespace) {
		return cache.get(localName, namespace);
	}

	public QName createQName(String localName) {
		return cache.get(localName);
	}

	public QName createQName(String name, String prefix, String uri) {
		return cache.get(name, Namespace.get(prefix, uri));
	}

	public QName createQName(String qualifiedName, String uri) {
		return cache.get(qualifiedName, uri);
	}

	/**
	 * <p>
	 * <code>createXPath</code> parses an XPath expression and creates a new XPath <code>XPath</code> instance.
	 * </p>
	 * 
	 * @param xpathExpression is the XPath expression to create
	 * 
	 * @return a new <code>XPath</code> instance
	 * 
	 * @throws InvalidXPathException if the XPath expression is invalid
	 */
	public XPath createXPath(String xpathExpression) throws InvalidXPathException {
		DefaultXPath xpath = new DefaultXPath(xpathExpression);

		if (xpathNamespaceURIs != null) {
			xpath.setNamespaceURIs(xpathNamespaceURIs);
		}

		return xpath;
	}

	/**
	 * <p>
	 * <code>createXPath</code> parses an XPath expression and creates a new XPath <code>XPath</code> instance.
	 * </p>
	 * 
	 * @param xpathExpression is the XPath expression to create
	 * @param variableContext is the variable context to use when evaluating the XPath
	 * 
	 * @return a new <code>XPath</code> instance
	 */
	public XPath createXPath(String xpathExpression, VariableContext variableContext) {
		XPath xpath = createXPath(xpathExpression);
		xpath.setVariableContext(variableContext);

		return xpath;
	}

	/**
	 * <p>
	 * <code>createXPathFilter</code> parses a NodeFilter from the given XPath filter expression. XPath filter
	 * expressions occur within XPath expressions such as <code>self::node()[ filterExpression ]</code>
	 * </p>
	 * 
	 * @param xpathFilterExpression is the XPath filter expression to create
	 * @param variableContext is the variable context to use when evaluating the XPath
	 * 
	 * @return a new <code>NodeFilter</code> instance
	 */
	public NodeFilter createXPathFilter(String xpathFilterExpression, VariableContext variableContext) {
		XPath answer = createXPath(xpathFilterExpression);

		// DefaultXPath answer = new DefaultXPath( xpathFilterExpression );
		answer.setVariableContext(variableContext);

		return answer;
	}

	/**
	 * <p>
	 * <code>createXPathFilter</code> parses a NodeFilter from the given XPath filter expression. XPath filter
	 * expressions occur within XPath expressions such as <code>self::node()[ filterExpression ]</code>
	 * </p>
	 * 
	 * @param xpathFilterExpression is the XPath filter expression to create
	 * 
	 * @return a new <code>NodeFilter</code> instance
	 */
	public NodeFilter createXPathFilter(String xpathFilterExpression) {
		return createXPath(xpathFilterExpression);

		// return new DefaultXPath( xpathFilterExpression );
	}

	/**
	 * <p>
	 * <code>createPattern</code> parses the given XPath expression to create an XSLT style {@link Pattern}instance
	 * which can then be used in an XSLT processing model.
	 * </p>
	 * 
	 * @param xpathPattern is the XPath pattern expression to create
	 * 
	 * @return a new <code>Pattern</code> instance
	 */
	public Pattern createPattern(String xpathPattern) {
		return new XPathPattern(xpathPattern);
	}

	// Properties
	// -------------------------------------------------------------------------

	/**
	 * Returns a list of all the QName instances currently used by this document factory
	 * 
	 * @return DOCUMENT ME!
	 */
	public List getQNames() {
		return cache.getQNames();
	}

	/**
	 * DOCUMENT ME!
	 * 
	 * @return the Map of namespace URIs that will be used by by XPath expressions to resolve namespace prefixes into
	 *         namespace URIs. The map is keyed by namespace prefix and the value is the namespace URI. This value could
	 *         well be null to indicate no namespace URIs are being mapped.
	 */
	public Map getXPathNamespaceURIs() {
		return xpathNamespaceURIs;
	}

	/**
	 * Sets the namespace URIs to be used by XPath expressions created by this factory or by nodes associated with this
	 * factory. The keys are namespace prefixes and the values are namespace URIs.
	 * 
	 * @param namespaceURIs DOCUMENT ME!
	 */
	public void setXPathNamespaceURIs(Map namespaceURIs) {
		this.xpathNamespaceURIs = namespaceURIs;
	}

	// Implementation methods
	// -------------------------------------------------------------------------

	/**
	 * <p>
	 * <code>createSingleton</code> creates the singleton instance from the given class name.
	 * </p>
	 * 
	 * @param className is the name of the DocumentFactory class to use
	 * 
	 * @return a new singleton instance.
	 */
	protected static DocumentFactory createSingleton(String className) {
		// let's try and class load an implementation?
		try {
			// I'll use the current class loader
			// that loaded me to avoid problems in J2EE and web apps
			Class theClass = Class.forName(className, true, DocumentFactory.class.getClassLoader());

			return (DocumentFactory) theClass.newInstance();
		}
		catch (Throwable e) {
			System.out.println("WARNING: Cannot load DocumentFactory: " + className);

			return new DocumentFactory();
		}
	}

	/**
	 * DOCUMENT ME!
	 * 
	 * @param qname DOCUMENT ME!
	 * 
	 * @return the cached QName instance if there is one or adds the given qname to the cache if not
	 */
	protected QName intern(QName qname) {
		return cache.intern(qname);
	}

	/**
	 * Factory method to create the QNameCache. This method should be overloaded if you wish to use your own derivation
	 * of QName.
	 * 
	 * @return DOCUMENT ME!
	 */
	protected QNameCache createQNameCache() {
		return new QNameCache(this);
	}

	private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
		in.defaultReadObject();
		init();
	}

	protected void init() {
		cache = createQNameCache();
	}
}

/*
 * Redistribution and use of this software and associated documentation ("Software"), with or without modification, are
 * permitted provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain copyright statements and notices. Redistributions must also contain a
 * copy of this document.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 * following disclaimer in the documentation and/or other materials provided with the distribution.
 * 
 * 3. The name "DOM4J" must not be used to endorse or promote products derived from this Software without prior written
 * permission of MetaStuff, Ltd. For written permission, please contact dom4j-info@metastuff.com.
 * 
 * 4. Products derived from this Software may not be called "DOM4J" nor may "DOM4J" appear in their names without prior
 * written permission of MetaStuff, Ltd. DOM4J is a registered trademark of MetaStuff, Ltd.
 * 
 * 5. Due credit should be given to the DOM4J Project - http://www.dom4j.org
 * 
 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 * 
 * Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved.
 */
